package utils;

import org.eclipse.jgit.api.CheckoutCommand;
import org.eclipse.jgit.api.CommitCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.ListBranchCommand.ListMode;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.PushResult;
import org.eclipse.jgit.transport.RemoteRefUpdate;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

@Component
public final class GitUtil {

	private static Logger logger = LoggerFactory.getLogger(GitUtil.class);
	private static final int NUMBERS = 3;

	private GitUtil(){

	}

	/**
	 *
	 * 方法描述: 克隆远程仓库到本地<br>
	 * 方法备注: <br>
	 *
	 * @param remoteRepoURI
	 * @param localRepoPath
	 * @param userName
	 * @param password
	 *            创建人：weixiang.huan <br>
	 *            创建时间：2017年5月8日 上午9:11:28<br>
	 *            修改人：<br>
	 *            修改时间：<br>
	 */
	public static void clone(String remoteRepoURI, String localRepoPath, String gitBranch, String userName,
			String password, String mail) {
		if (FileUtil.isExit(localRepoPath)) {
			FileUtil.deleteFolder(new File(localRepoPath));
		}
		createBranch(remoteRepoURI, localRepoPath, gitBranch, userName, password, mail);
		if (FileUtil.isExit(localRepoPath)) {
			FileUtil.deleteFolder(new File(localRepoPath));
		}

		setupRepo(remoteRepoURI, localRepoPath, gitBranch, userName, password);
	}

	/**
	 *
	 * 方法描述: 推送本地修改到远程客户端<br>
	 * 方法备注: <br>
	 *
	 * @param localRepoGitConfig
	 * @param commitMessage
	 * @param userName
	 * @param password
	 * @param mail
	 * @param uploadFolder
	 *            创建人：weixiang.huan <br>
	 *            创建时间：2017年5月8日 上午9:12:29<br>
	 *            修改人：<br>
	 *            修改时间：<br>
	 */
	public static String commit(String localRepoGitConfig, String commitMessage, String userName, String password,
			String mail, String uploadFolder) {
		return commitFiles(localRepoGitConfig, commitMessage, userName, password, mail, uploadFolder);
	}

	/**
	 *
	 * 方法描述: 建立与远程仓库的联系，仅需要执行一次<br>
	 * 方法备注: <br>
	 *
	 * @param remoteRepoURI
	 * @param localRepoPath
	 * @param userName
	 * @param password
	 *            创建人：weixiang.huan <br>
	 *            创建时间：2017年5月8日 上午9:12:10<br>
	 *            修改人：<br>
	 *            修改时间：<br>
	 */
	public static void setupRepo(String remoteRepoURI, String localRepoPath, String gitBranch, String userName,
			String password) {
		try (Git git = Git.cloneRepository().setURI(remoteRepoURI).setBranch(gitBranch)
                .setDirectory(new File(localRepoPath))
                .setCredentialsProvider(new UsernamePasswordCredentialsProvider(userName, password)).call()) {
//			HttpConnectionFactory httpConnectionFactory = HttpTransport.getConnectionFactory();
//			HttpTransport.setConnectionFactory(httpConnectionFactory);
//			HttpTransport.setConnectionFactory(new InsecureHttpConnectionFactory());
		} catch (GitAPIException e1) {
			logger.error("git克隆出错：{}", e1);
			throw new RuntimeException("git克隆出错："+e1.getMessage());
		}
	}

	/**
	 *
	 * 方法描述: 建立与远程仓库的联系,新建分支<br>
	 * 方法备注: <br>
	 *
	 * @param remoteRepoURI
	 * @param localRepoPath
	 * @param userName
	 * @param password
	 */
	public static void createBranch(String remoteRepoURI, String localRepoPath, String gitBranch, String userName,
			String password, String mail) {

		try (Git git = Git.cloneRepository().setURI(remoteRepoURI).setDirectory(new File(localRepoPath))
                .setCredentialsProvider(new UsernamePasswordCredentialsProvider(userName, password)).call()) {
			String newBranchIndex = "refs/remotes/origin/" + gitBranch;
			// 检查分支是否已经存在
			Boolean branchExist = false;
			List<Ref> refs = git.branchList().setListMode(ListMode.ALL).call();
			if(refs.isEmpty()) {
                new File(localRepoPath + "//.gitignore").createNewFile();
//                git.push().setCredentialsProvider(new UsernamePasswordCredentialsProvider(userName, password)).call();
                commitFiles(git, "create .gitignore and master branch", userName, password, mail,
                        ".gitignore");
                if(!"master".equals(gitBranch)) {
                    createBranch(git, gitBranch, userName, password);
                }
			    return;
			}
			for (Ref ref : refs) {
				if (ref.getName().equals(newBranchIndex)) {
					branchExist = true;
				}
			}
			if (!branchExist) {
                createBranch(git, gitBranch, userName, password);
			}
		} catch (Exception e1) {
			logger.error("git新建分支出错：{}", e1);
//			throw new RuntimeException("git新建分支出错："+e1.getMessage());
			throw new RuntimeException("git新建分支出错");
		}
	}

	private static void createBranch(Git git, String gitBranch, String userName, String password) throws GitAPIException {
        // 新建分支
        Ref ref = git.branchCreate().setName(gitBranch).call();
        // 推送到远程
        git.push().setCredentialsProvider(new UsernamePasswordCredentialsProvider(userName, password)).add(ref).call();
    }

	/**
	 *
	 * 方法描述: 推送本地修改到远程客户端<br>
	 * 方法备注: <br>
	 *
	 * @param localRepoGitConfig
	 * @param commitMessage
	 * @param userName
	 * @param password
	 * @param mail
	 * @param uploadFolder
	 *            创建人：weixiang.huan <br>
	 *            创建时间：2017年5月8日 上午9:13:29<br>
	 *            修改人：<br>
	 *            修改时间：<br>
	 */
	public static String commitFiles(String localRepoGitConfig, String commitMessage, String userName, String password,
			String mail, String uploadFolder) {
		try (Git git = Git.open(new File(localRepoGitConfig))) {
			return commitFiles(git, commitMessage, userName, password, mail, uploadFolder);
		} catch (IOException | GitAPIException e) {
			logger.error("git上传失败：{}", e.getMessage());
//			throw new RuntimeException("git上传失败", e);
            throw new RuntimeException("git上传失败");
		}
	}

    private static String commitFiles(Git git, String commitMessage, String userName, String password,
                                      String mail, String uploadFolder) throws GitAPIException{
        git.add().addFilepattern(uploadFolder).call();
        // 提交
        RevCommit revCommit = git.commit().setMessage(commitMessage).setCommitter(userName, mail).call();
        // 推送到远程
        Iterable<PushResult> pushResult =
                git.push().setCredentialsProvider(new UsernamePasswordCredentialsProvider(userName, password)).call();
        Iterator iter = pushResult.iterator();
        while(iter.hasNext()){
            PushResult presult = (PushResult) iter.next();
            Collection<RemoteRefUpdate> update = presult.getRemoteUpdates();
            for (RemoteRefUpdate remoteRefUpdate : update) {
                logger.info("print log:="+remoteRefUpdate.getStatus()+","+remoteRefUpdate.getMessage());
                if(!remoteRefUpdate.getStatus().equals(RemoteRefUpdate.Status.OK)){
                    retryPushFileToGit(git,userName,password);
                }
            }
        }
        logger.info("commitId:="+revCommit.getName());
        return revCommit.getName();
    }

	private static void retryPushFileToGit(Git git,String userName, String password){
		//重试三次
		boolean flag = false;
		for(int i=1;i<NUMBERS;i++){
			logger.info("循环重试推送中。。。。"+i);
			try {
				git.pull().setRemote("origin").
						setCredentialsProvider(new UsernamePasswordCredentialsProvider(userName, password)).call();
				Iterable<PushResult> pushResult1 =
						git.push().setCredentialsProvider(new UsernamePasswordCredentialsProvider(userName, password)).call();
				Iterator iter = pushResult1.iterator();
				while(iter.hasNext()){
					 PushResult presult = (PushResult) iter.next();
				     Collection<RemoteRefUpdate> update = presult.getRemoteUpdates();
				     for (RemoteRefUpdate remoteRefUpdate : update) {
						logger.info("重试状态:"+remoteRefUpdate.getStatus()+","+remoteRefUpdate.getMessage());
						if(remoteRefUpdate.getStatus().equals(RemoteRefUpdate.Status.OK)){
							flag = true;
							return;
						}
					}
				}
			} catch (Exception e) {
				logger.error("git上传失败：{}", e);
//				throw new RuntimeException("尝试再次操作git异常", e);
                throw new RuntimeException("git上传失败");
			}
		}
		if(!flag){
			logger.error("git上传失败：尝试3次后上传失败");
//			throw new RuntimeException("git上传失败");
            throw new RuntimeException("git上传失败：尝试3次后上传失败");
		}
	}

	/**
	 * 回滚到指定版本的上一个版本
	 */
	public static String rollBackRevision(String localRepoGitConfig, String revision, String commitMessage,
			String userName, String password, String mail, String uploadFolder) {
		try (Git git = Git.open(new File(localRepoGitConfig))) {
			// 注意：下面的reset命令会将暂存区的内容恢复到指定（revesion）的状态，相当于取消add命令的操作
			/*
			 * Repository repository = git.getRepository();
			 *
			 * RevWalk walk = new RevWalk(repository); ObjectId objId =
			 * repository.resolve(revision); RevCommit revCommit =
			 * walk.parseCommit(objId); String preVision =
			 * revCommit.getParent(0).getName(); ResetCommand resetCmd =
			 * git.reset(); for (String file : files) { resetCmd.addPath(file);
			 * } resetCmd.setRef(preVision).call(); repository.close();
			 */

			// checkout操作会丢失工作区的数据，暂存区和工作区的数据会恢复到指定（revision）的版本内容
			CheckoutCommand checkoutCmd = git.checkout();
			checkoutCmd.addPath(uploadFolder);

			// 加了“^”表示指定版本的前一个版本，如果没有上一版本，在命令行中会报错，例如：error: pathspec '4.vm' did
			// not match any file(s) known to git.
			checkoutCmd.setStartPoint(revision);
			checkoutCmd.call();
			// 重新提交一次
			CommitCommand commitCmd = git.commit();
			// 提交
			RevCommit revCommit = commitCmd.setMessage(commitMessage).setCommitter(userName, mail).call();
			// 推送到远程
			git.push().setCredentialsProvider(new UsernamePasswordCredentialsProvider(userName, password)).call();
			return revCommit.getName();
		} catch (Exception e) {
			logger.error("git回滚失败：{}", e.getMessage());
			throw new RuntimeException("git回滚失败", e);
		}

	}

	/**
	 * 删除文件，git上传操作
	 *
	 * @param localRepoGitConfig
	 * @param commitMessage
	 * @param userName
	 * @param password
	 * @param mail
	 * @param uploadFolder
	 */
	public static String commitDeleteFiles(String localRepoGitConfig, String commitMessage, String userName,
			String password, String mail, String uploadFolder) {
		try (Git git = Git.open(new File(localRepoGitConfig))) {
			git.add().setUpdate(true).addFilepattern(uploadFolder).call();
			// 提交
			RevCommit revCommit = git.commit().setMessage(commitMessage).setCommitter(userName, mail).call();
			// 推送到远程
			Iterable<PushResult> pushResult =
					git.push().setCredentialsProvider(new UsernamePasswordCredentialsProvider(userName, password)).call();
			Iterator iter = pushResult.iterator();
			while(iter.hasNext()){
				PushResult presult = (PushResult) iter.next();
				Collection<RemoteRefUpdate> update = presult.getRemoteUpdates();
				for (RemoteRefUpdate remoteRefUpdate : update) {
					logger.info("print log:="+remoteRefUpdate.getStatus()+","+remoteRefUpdate.getMessage());
					if(!remoteRefUpdate.getStatus().equals(RemoteRefUpdate.Status.OK)){
						retryPushFileToGit(git,userName,password);
					}
				}
			}
			return revCommit.getName();
		} catch (IOException | GitAPIException e) {
			logger.error("git上传失败：{}", e.getMessage());
			throw new RuntimeException("git上传失败", e);
		}
	}

	/**
	 *
	 * 方法描述: 拉取分支到本地<br>
	 * 方法备注: <br>
	 *
	 * @param cloneURL
	 * @param localRepoPath
	 * @param localRepoGitConfig
	 * @param userName
	 * @param password
	 * @return 创建人：weixiang.huan <br>
	 *         创建时间：2017年5月8日 上午9:15:31<br>
	 *         修改人：<br>
	 *         修改时间：<br>
	 */
	public static boolean pullBranchToLocal(String cloneURL, String localRepoPath, String localRepoGitConfig,
			String userName, String password) {
		boolean resultFlag = false;
		String[] splitURL = cloneURL.split(" ");
		String branchName = splitURL[1];
		String fileDir = localRepoPath + "/" + branchName;
		// 检查目标文件夹是否存在
		File file = new File(fileDir);
		if (file.exists()) {
			FileUtil.deleteFolder(file);
		}
		try {
			Git.open(new File(localRepoGitConfig));
			Git.cloneRepository().setURI(cloneURL).setBranch(branchName).setDirectory(file)
					.setCredentialsProvider(new UsernamePasswordCredentialsProvider(userName, password)).call();
			resultFlag = true;
		} catch (IOException | GitAPIException e) {
			logger.error(e.getMessage());
		}
		return resultFlag;
	}

	public static void checkout(String localRepoGitConfig, String revision, String uploadFolder) {
		try {
			Git git = Git.open(new File(localRepoGitConfig));

			// checkout操作会丢失工作区的数据，暂存区和工作区的数据会恢复到指定（revision）的版本内容
			CheckoutCommand checkoutCmd = git.checkout();
			checkoutCmd.addPath(uploadFolder);

			// 加了“^”表示指定版本的前一个版本，如果没有上一版本，在命令行中会报错，例如：error: pathspec '4.vm' did
			// not match any file(s) known to git.
			checkoutCmd.setStartPoint(revision);
			checkoutCmd.call();

		} catch (Exception e) {
			logger.error("git回滚失败：{}", e.getMessage());
			throw new RuntimeException("git回滚失败", e);
		}

	}


    public static void main(String[] args) {
        clone("https://git.pcitc.com/zhihui.zhou/test-branch", "e:\\test123", "master", "zhihui.zhou", "shYK7890", "zhihui.zhou@pcitc.com");
	}
}
